#include "Chunk/Chunk.h"
#include<baseClass/GLog.h>
#include "MCWorld.h"
ThreadPool Chunk::ChunkthreadPool(4);

int Chunk::ChunkSizeX = 16;
int Chunk::ChunkSizeY = 16;
int Chunk::ChunkSizeZ = 256;

Chunk::Chunk(ChunkManager * Manager):M_ChunkManager(Manager)
{
    this->blocks = vector<vector<vector<BlockBase *>>>(ChunkSizeX, 
                    vector<vector<BlockBase *>>(ChunkSizeY, 
                    vector<BlockBase* >(ChunkSizeZ, nullptr)));
    chunkGenerator = ChunkGenerator::CreateGenerator(MCWorld::GetSeed(),this->chunkType);
    
}

Chunk::Chunk(ChunkManager * Manager,int x, int y):M_ChunkManager(Manager)
{
    this->x = x;
    this->y = y;
    this->blocks =  vector<vector<vector<BlockBase * >>>(ChunkSizeX, 
                    vector<vector<BlockBase *>>(ChunkSizeY, 
                    vector<BlockBase *>(ChunkSizeZ, nullptr)));
    chunkGenerator = ChunkGenerator::CreateGenerator(MCWorld::GetSeed(),this->chunkType);

}

Chunk::Chunk(ChunkManager *Manager, int x, int y, ChunkType type):M_ChunkManager(Manager)
{
    this->x = x;
    this->y = y;
    this->chunkType = type;
    this->blocks =  vector<vector<vector<BlockBase * >>>(ChunkSizeX, 
                    vector<vector<BlockBase *>>(ChunkSizeY, 
                    vector<BlockBase *>(ChunkSizeZ, nullptr)));

    chunkGenerator = ChunkGenerator::CreateGenerator(MCWorld::GetSeed(),this->chunkType);
}

unordered_map<Texture2D * , vector<BlockVertex>> Chunk::GetRenderData(bool SmoothLighting)
{
    // auto start = std::chrono::high_resolution_clock::now();
    unordered_map<Texture2D *, vector<BlockVertex>> res;
    for (int x = 0; x < ChunkSizeX; x++)
    {
        for (int y = 0; y < ChunkSizeY; y++)
        {
            for (int z = 0; z < ChunkSizeZ; z++)
            {
                auto block = this->blocks[x][y][z];
                if (blocks[x][y][z])
                {
                
                    unordered_map<Texture2D * , vector<BlockVertex>> faceV = 
                    block->GetfaceVandT(GetNeedRenderface(x, y, z));

                    int realx = this->x * ChunkSizeX + x;
                    int realy = this->y * ChunkSizeY + y;

                    for(auto it = faceV.begin(); it != faceV.end(); it ++)
                    {
                        for (auto v : it->second)
                        {
                            v.position += glm::vec3(realx, realy, z);

                            res[it->first].push_back(v);
                        }

                    }
                }

                
            }
        }
    }
    return res;
}


int Chunk::GetNeedRenderface(int x, int y, int z)
{
    int res = 0;
    auto realpos = this->GetRealWorldPos(glm::ivec3(x, y, z));

    auto F = [&](int dx, int dy,int dz)->BlockBase * {
        return this->M_ChunkManager->GetBlock(glm::ivec3(realpos.x + dx, realpos.y + dy, z + dz));
    };
    if(F(-1,0,0) == nullptr) res |= BlockBase::faceid::X_;
    if(F(1,0,0) == nullptr) res |= BlockBase::faceid::X;
    if(F(0,-1,0) == nullptr) res |= BlockBase::faceid::Y_;
    if(F(0,1,0) == nullptr) res |= BlockBase::faceid::Y;
    if(F(0,0,-1) == nullptr) res |= BlockBase::faceid::Z_;
    if(F(0,0,1) == nullptr) res |= BlockBase::faceid::Z;
    return res;
}



Chunk::~Chunk()
{
    //释放Meshs
    if(Meshs)
    {
        delete Meshs;
    }
    //释放blocks
    for(int i = 0; i < ChunkSizeX; i++)
    {
        for(int j = 0; j < ChunkSizeY; j++)
        {
            for(int k = 0; k < ChunkSizeZ; k++)
            {
                if(blocks[i][j][k])
                {
                    delete blocks[i][j][k];
                }
            }
        }
    }

}


void Chunk::Render(Blockshader &shader)
{
    switch (chunkDataState)
    {
    case ChunkDataState::None:
        {
            chunkDataState = ChunkDataState::Change;
        }
    [[fallthrough]]
    case ChunkDataState::Change:
    [[fallthrough]]
    case ChunkDataState::GenerateChunksDataFinsh:
        {
            chunkDataState = ChunkDataState::GenerateRenderData;
            if(Meshs)
            {
                delete Meshs;
            }
            Meshs = nullptr;
            ChunkthreadPool.submit("ChunkRender",[this](){
                this->RenderData = this->GetRenderData();
                chunkDataState = ChunkDataState::GenerateRenderDataFinsh;
            });
        }
        break;
    case ChunkDataState::GenerateRenderDataFinsh:
        {
            Meshs = new BlockMesh(std::move(this->RenderData));
            chunkDataState = ChunkDataState::NoneChange;
        }
    [[fallthrough]]
    case ChunkDataState::NoneChange:
        {
            Meshs->Render(shader);
            break;
        }
    default:
        break;
    }
}


void Chunk::UpdataLightIntensity()
{
    //TODO:更新光照
}

void Chunk::SetBrightnessLevel(int x, int y, int z, uint8_t brightnessLevel,int face)
{
    if(this->blocks[x][y][z] == nullptr)
    {
        return;
    }
    this->chunkDataState = ChunkDataState::Change;
    
    if(face & BlockBase::faceid::X)
    {
        this->blocks[x][y][z]->brightnessLevel[0] = brightnessLevel;
    }
    if(face & BlockBase::faceid::Y)
    {
        this->blocks[x][y][z]->brightnessLevel[1] = brightnessLevel;
    }
    if(face & BlockBase::faceid::Z)
    {
        this->blocks[x][y][z]->brightnessLevel[2] = brightnessLevel;
    }
    if(face & BlockBase::faceid::X_)
    {
        this->blocks[x][y][z]->brightnessLevel[3] = brightnessLevel;
    }
    if(face & BlockBase::faceid::Y_)
    {
        this->blocks[x][y][z]->brightnessLevel[4] = brightnessLevel;
    }
    if(face & BlockBase::faceid::Z_)
    {
        this->blocks[x][y][z]->brightnessLevel[5] = brightnessLevel;
    }
}


void Chunk::GenerateChunk()
{
    this->chunkDataState = ChunkDataState::GenerateChunksData;

    auto name ="Chunk"+ std::to_string(this->x) + "_" + std::to_string(this->y);
    ChunkthreadPool.submit(name,[this](){
        this->chunkGenerator->Generate(this->blocks, this->x, this->y);
        this->chunkDataState = ChunkDataState::GenerateChunksDataFinsh;

        auto F = [&](int dx, int dy)
        {
              auto c =  this->M_ChunkManager->GetChunk(this->x + dx, this->y+ dy);
            if(c) c->SetRenderDataChange();
        };

        F(1, 0);
        F(-1, 0);
        F(0, 1);
        F(0, -1);
    });
}

BlockBase * Chunk::GetBlock(int x, int y, int z)
{
    if(this->chunkDataState == ChunkDataState::GenerateChunksData)
    {
        // ERROR("GetBlock in GenerateChunksData");
        return nullptr;
    }
    
    if(x < 0 || x >= ChunkSizeX || y < 0 || y >= ChunkSizeY || z < 0 || z >= ChunkSizeZ)
    {
        return nullptr;
    }
    return this->blocks[x][y][z];
}
